/*
 * Javassist, a Java-bytecode translator toolkit.
 * Copyright (C) 1999- Shigeru Chiba. All Rights Reserved.
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (the "License"); you may not use this file except in compliance with
 * the License.  Alternatively, the contents of this file may be used under
 * the terms of the GNU Lesser General Public License Version 2.1 or later,
 * or the Apache License Version 2.0.
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 */

package com.feilong.lib.javassist.bytecode.stackmap;

import com.feilong.lib.javassist.ClassPool;
import com.feilong.lib.javassist.bytecode.BadBytecode;
import com.feilong.lib.javassist.bytecode.ByteArray;
import com.feilong.lib.javassist.bytecode.ConstPool;
import com.feilong.lib.javassist.bytecode.Descriptor;
import com.feilong.lib.javassist.bytecode.Opcode;

/*
 * A class for performing abstract interpretation.
 * See also MapMaker class. 
 */

public abstract class Tracer implements TypeTag{

    protected ClassPool  classPool;

    protected ConstPool  cpool;

    protected String     returnType; // used as the type of ARETURN

    protected int        stackTop;

    protected TypeData[] stackTypes;

    protected TypeData[] localsTypes;

    public Tracer(ClassPool classes, ConstPool cp, int maxStack, int maxLocals, String retType){
        classPool = classes;
        cpool = cp;
        returnType = retType;
        stackTop = 0;
        stackTypes = TypeData.make(maxStack);
        localsTypes = TypeData.make(maxLocals);
    }

    public Tracer(Tracer t){
        classPool = t.classPool;
        cpool = t.cpool;
        returnType = t.returnType;
        stackTop = t.stackTop;
        stackTypes = TypeData.make(t.stackTypes.length);
        localsTypes = TypeData.make(t.localsTypes.length);
    }

    /**
     * Does abstract interpretation on the given bytecode instruction.
     * It records whether or not a local variable (i.e. register) is accessed.
     * If the instruction requires that a local variable or
     * a stack element has a more specific type, this method updates the
     * type of it.
     *
     * @param pos
     *            the position of the instruction.
     * @return the size of the instruction at POS.
     */
    protected int doOpcode(int pos,byte[] code) throws BadBytecode{
        try{
            int op = code[pos] & 0xff;
            if (op < 54){
                return doOpcode0_53(pos, code, op);
            }
            if (op < 96){
                return doOpcode54_95(pos, code, op);
            }
            if (op < 148){
                return doOpcode96_147(pos, code, op);
            }
            return doOpcode148_201(pos, code, op);
        }catch (ArrayIndexOutOfBoundsException e){
            throw new BadBytecode("inconsistent stack height " + e.getMessage(), e);
        }
    }

    protected void visitBranch(int pos,byte[] code,int offset) throws BadBytecode{
    }

    protected void visitGoto(int pos,byte[] code,int offset) throws BadBytecode{
    }

    protected void visitReturn(int pos,byte[] code) throws BadBytecode{
    }

    protected void visitThrow(int pos,byte[] code) throws BadBytecode{
    }

    /**
     * @param pos
     *            the position of TABLESWITCH
     * @param code
     *            bytecode
     * @param n
     *            the number of case labels
     * @param offsetPos
     *            the position of the branch-target table.
     * @param defaultOffset
     *            the offset to the default branch target.
     */
    protected void visitTableSwitch(int pos,byte[] code,int n,int offsetPos,int defaultOffset) throws BadBytecode{
    }

    /**
     * @param pos
     *            the position of LOOKUPSWITCH
     * @param code
     *            bytecode
     * @param n
     *            the number of case labels
     * @param pairsPos
     *            the position of the table of pairs of a value and a branch target.
     * @param defaultOffset
     *            the offset to the default branch target.
     */
    protected void visitLookupSwitch(int pos,byte[] code,int n,int pairsPos,int defaultOffset) throws BadBytecode{
    }

    /**
     * Invoked when the visited instruction is jsr.
     * Java6 or later does not allow using RET.
     */
    protected void visitJSR(int pos,byte[] code) throws BadBytecode{
        /*
         * Since JSR pushes a return address onto the operand stack,
         * the stack map at the entry point of a subroutine is
         * stackTypes resulting after executing the following code:
         *
         * stackTypes[stackTop++] = TOP;
         */
    }

    /**
     * Invoked when the visited instruction is ret or wide ret.
     * Java6 or later does not allow using RET.
     */
    protected void visitRET(int pos,byte[] code) throws BadBytecode{
    }

    private int doOpcode0_53(int pos,byte[] code,int op) throws BadBytecode{
        int reg;
        TypeData[] stackTypes = this.stackTypes;
        switch (op) {
            case Opcode.NOP:
                break;
            case Opcode.ACONST_NULL:
                stackTypes[stackTop++] = new TypeData.NullType();
                break;
            case Opcode.ICONST_M1:
            case Opcode.ICONST_0:
            case Opcode.ICONST_1:
            case Opcode.ICONST_2:
            case Opcode.ICONST_3:
            case Opcode.ICONST_4:
            case Opcode.ICONST_5:
                stackTypes[stackTop++] = INTEGER;
                break;
            case Opcode.LCONST_0:
            case Opcode.LCONST_1:
                stackTypes[stackTop++] = LONG;
                stackTypes[stackTop++] = TOP;
                break;
            case Opcode.FCONST_0:
            case Opcode.FCONST_1:
            case Opcode.FCONST_2:
                stackTypes[stackTop++] = FLOAT;
                break;
            case Opcode.DCONST_0:
            case Opcode.DCONST_1:
                stackTypes[stackTop++] = DOUBLE;
                stackTypes[stackTop++] = TOP;
                break;
            case Opcode.BIPUSH:
            case Opcode.SIPUSH:
                stackTypes[stackTop++] = INTEGER;
                return op == Opcode.SIPUSH ? 3 : 2;
            case Opcode.LDC:
                doLDC(code[pos + 1] & 0xff);
                return 2;
            case Opcode.LDC_W:
            case Opcode.LDC2_W:
                doLDC(ByteArray.readU16bit(code, pos + 1));
                return 3;
            case Opcode.ILOAD:
                return doXLOAD(INTEGER, code, pos);
            case Opcode.LLOAD:
                return doXLOAD(LONG, code, pos);
            case Opcode.FLOAD:
                return doXLOAD(FLOAT, code, pos);
            case Opcode.DLOAD:
                return doXLOAD(DOUBLE, code, pos);
            case Opcode.ALOAD:
                return doALOAD(code[pos + 1] & 0xff);
            case Opcode.ILOAD_0:
            case Opcode.ILOAD_1:
            case Opcode.ILOAD_2:
            case Opcode.ILOAD_3:
                stackTypes[stackTop++] = INTEGER;
                break;
            case Opcode.LLOAD_0:
            case Opcode.LLOAD_1:
            case Opcode.LLOAD_2:
            case Opcode.LLOAD_3:
                stackTypes[stackTop++] = LONG;
                stackTypes[stackTop++] = TOP;
                break;
            case Opcode.FLOAD_0:
            case Opcode.FLOAD_1:
            case Opcode.FLOAD_2:
            case Opcode.FLOAD_3:
                stackTypes[stackTop++] = FLOAT;
                break;
            case Opcode.DLOAD_0:
            case Opcode.DLOAD_1:
            case Opcode.DLOAD_2:
            case Opcode.DLOAD_3:
                stackTypes[stackTop++] = DOUBLE;
                stackTypes[stackTop++] = TOP;
                break;
            case Opcode.ALOAD_0:
            case Opcode.ALOAD_1:
            case Opcode.ALOAD_2:
            case Opcode.ALOAD_3:
                reg = op - Opcode.ALOAD_0;
                stackTypes[stackTop++] = localsTypes[reg];
                break;
            case Opcode.IALOAD:
                stackTypes[--stackTop - 1] = INTEGER;
                break;
            case Opcode.LALOAD:
                stackTypes[stackTop - 2] = LONG;
                stackTypes[stackTop - 1] = TOP;
                break;
            case Opcode.FALOAD:
                stackTypes[--stackTop - 1] = FLOAT;
                break;
            case Opcode.DALOAD:
                stackTypes[stackTop - 2] = DOUBLE;
                stackTypes[stackTop - 1] = TOP;
                break;
            case Opcode.AALOAD:{
                int s = --stackTop - 1;
                TypeData data = stackTypes[s];
                stackTypes[s] = TypeData.ArrayElement.make(data);
                break;
            }
            case Opcode.BALOAD:
            case Opcode.CALOAD:
            case Opcode.SALOAD:
                stackTypes[--stackTop - 1] = INTEGER;
                break;
            default:
                throw new RuntimeException("fatal");
        }

        return 1;
    }

    private void doLDC(int index){
        TypeData[] stackTypes = this.stackTypes;
        int tag = cpool.getTag(index);
        if (tag == ConstPool.CONST_String){
            stackTypes[stackTop++] = new TypeData.ClassName("java.lang.String");
        }else if (tag == ConstPool.CONST_Integer){
            stackTypes[stackTop++] = INTEGER;
        }else if (tag == ConstPool.CONST_Float){
            stackTypes[stackTop++] = FLOAT;
        }else if (tag == ConstPool.CONST_Long){
            stackTypes[stackTop++] = LONG;
            stackTypes[stackTop++] = TOP;
        }else if (tag == ConstPool.CONST_Double){
            stackTypes[stackTop++] = DOUBLE;
            stackTypes[stackTop++] = TOP;
        }else if (tag == ConstPool.CONST_Class){
            stackTypes[stackTop++] = new TypeData.ClassName("java.lang.Class");
        }else{
            throw new RuntimeException("bad LDC: " + tag);
        }
    }

    private int doXLOAD(TypeData type,byte[] code,int pos){
        int localVar = code[pos + 1] & 0xff;
        return doXLOAD(localVar, type);
    }

    private int doXLOAD(int localVar,TypeData type){
        stackTypes[stackTop++] = type;
        if (type.is2WordType()){
            stackTypes[stackTop++] = TOP;
        }

        return 2;
    }

    private int doALOAD(int localVar){
        stackTypes[stackTop++] = localsTypes[localVar];
        return 2;
    }

    private int doOpcode54_95(int pos,byte[] code,int op) throws BadBytecode{
        switch (op) {
            case Opcode.ISTORE:
                return doXSTORE(pos, code, INTEGER);
            case Opcode.LSTORE:
                return doXSTORE(pos, code, LONG);
            case Opcode.FSTORE:
                return doXSTORE(pos, code, FLOAT);
            case Opcode.DSTORE:
                return doXSTORE(pos, code, DOUBLE);
            case Opcode.ASTORE:
                return doASTORE(code[pos + 1] & 0xff);
            case Opcode.ISTORE_0:
            case Opcode.ISTORE_1:
            case Opcode.ISTORE_2:
            case Opcode.ISTORE_3:{
                int var = op - Opcode.ISTORE_0;
                localsTypes[var] = INTEGER;
                stackTop--;
            }
                break;
            case Opcode.LSTORE_0:
            case Opcode.LSTORE_1:
            case Opcode.LSTORE_2:
            case Opcode.LSTORE_3:{
                int var = op - Opcode.LSTORE_0;
                localsTypes[var] = LONG;
                localsTypes[var + 1] = TOP;
                stackTop -= 2;
            }
                break;
            case Opcode.FSTORE_0:
            case Opcode.FSTORE_1:
            case Opcode.FSTORE_2:
            case Opcode.FSTORE_3:{
                int var = op - Opcode.FSTORE_0;
                localsTypes[var] = FLOAT;
                stackTop--;
            }
                break;
            case Opcode.DSTORE_0:
            case Opcode.DSTORE_1:
            case Opcode.DSTORE_2:
            case Opcode.DSTORE_3:{
                int var = op - Opcode.DSTORE_0;
                localsTypes[var] = DOUBLE;
                localsTypes[var + 1] = TOP;
                stackTop -= 2;
            }
                break;
            case Opcode.ASTORE_0:
            case Opcode.ASTORE_1:
            case Opcode.ASTORE_2:
            case Opcode.ASTORE_3:{
                int var = op - Opcode.ASTORE_0;
                doASTORE(var);
                break;
            }
            case Opcode.IASTORE:
            case Opcode.LASTORE:
            case Opcode.FASTORE:
            case Opcode.DASTORE:
                stackTop -= (op == Opcode.LASTORE || op == Opcode.DASTORE) ? 4 : 3;
                break;
            case Opcode.AASTORE:
                TypeData.aastore(stackTypes[stackTop - 3], stackTypes[stackTop - 1], classPool);
                stackTop -= 3;
                break;
            case Opcode.BASTORE:
            case Opcode.CASTORE:
            case Opcode.SASTORE:
                stackTop -= 3;
                break;
            case Opcode.POP:
                stackTop--;
                break;
            case Opcode.POP2:
                stackTop -= 2;
                break;
            case Opcode.DUP:{
                int sp = stackTop;
                stackTypes[sp] = stackTypes[sp - 1];
                stackTop = sp + 1;
                break;
            }
            case Opcode.DUP_X1:
            case Opcode.DUP_X2:{
                int len = op - Opcode.DUP_X1 + 2;
                doDUP_XX(1, len);
                int sp = stackTop;
                stackTypes[sp - len] = stackTypes[sp];
                stackTop = sp + 1;
                break;
            }
            case Opcode.DUP2:
                doDUP_XX(2, 2);
                stackTop += 2;
                break;
            case Opcode.DUP2_X1:
            case Opcode.DUP2_X2:{
                int len = op - Opcode.DUP2_X1 + 3;
                doDUP_XX(2, len);
                int sp = stackTop;
                stackTypes[sp - len] = stackTypes[sp];
                stackTypes[sp - len + 1] = stackTypes[sp + 1];
                stackTop = sp + 2;
                break;
            }
            case Opcode.SWAP:{
                int sp = stackTop - 1;
                TypeData t = stackTypes[sp];
                stackTypes[sp] = stackTypes[sp - 1];
                stackTypes[sp - 1] = t;
                break;
            }
            default:
                throw new RuntimeException("fatal");
        }

        return 1;
    }

    private int doXSTORE(int pos,byte[] code,TypeData type){
        int index = code[pos + 1] & 0xff;
        return doXSTORE(index, type);
    }

    private int doXSTORE(int index,TypeData type){
        stackTop--;
        localsTypes[index] = type;
        if (type.is2WordType()){
            stackTop--;
            localsTypes[index + 1] = TOP;
        }

        return 2;
    }

    private int doASTORE(int index){
        stackTop--;
        // implicit upcast might be done.
        localsTypes[index] = stackTypes[stackTop];
        return 2;
    }

    private void doDUP_XX(int delta,int len){
        TypeData types[] = stackTypes;
        int sp = stackTop - 1;
        int end = sp - len;
        while (sp > end){
            types[sp + delta] = types[sp];
            sp--;
        }
    }

    private int doOpcode96_147(int pos,byte[] code,int op){
        if (op <= Opcode.LXOR){ // IADD...LXOR
            stackTop += Opcode.STACK_GROW[op];
            return 1;
        }

        switch (op) {
            case Opcode.IINC:
                // this does not call writeLocal().
                return 3;
            case Opcode.I2L:
                stackTypes[stackTop - 1] = LONG;
                stackTypes[stackTop] = TOP;
                stackTop++;
                break;
            case Opcode.I2F:
                stackTypes[stackTop - 1] = FLOAT;
                break;
            case Opcode.I2D:
                stackTypes[stackTop - 1] = DOUBLE;
                stackTypes[stackTop] = TOP;
                stackTop++;
                break;
            case Opcode.L2I:
                stackTypes[--stackTop - 1] = INTEGER;
                break;
            case Opcode.L2F:
                stackTypes[--stackTop - 1] = FLOAT;
                break;
            case Opcode.L2D:
                stackTypes[stackTop - 2] = DOUBLE;
                break;
            case Opcode.F2I:
                stackTypes[stackTop - 1] = INTEGER;
                break;
            case Opcode.F2L:
                stackTypes[stackTop - 1] = LONG;
                stackTypes[stackTop] = TOP;
                stackTop++;
                break;
            case Opcode.F2D:
                stackTypes[stackTop - 1] = DOUBLE;
                stackTypes[stackTop] = TOP;
                stackTop++;
                break;
            case Opcode.D2I:
                stackTypes[--stackTop - 1] = INTEGER;
                break;
            case Opcode.D2L:
                stackTypes[stackTop - 2] = LONG;
                break;
            case Opcode.D2F:
                stackTypes[--stackTop - 1] = FLOAT;
                break;
            case Opcode.I2B:
            case Opcode.I2C:
            case Opcode.I2S:
                break;
            default:
                throw new RuntimeException("fatal");
        }

        return 1;
    }

    private int doOpcode148_201(int pos,byte[] code,int op) throws BadBytecode{
        switch (op) {
            case Opcode.LCMP:
                stackTypes[stackTop - 4] = INTEGER;
                stackTop -= 3;
                break;
            case Opcode.FCMPL:
            case Opcode.FCMPG:
                stackTypes[--stackTop - 1] = INTEGER;
                break;
            case Opcode.DCMPL:
            case Opcode.DCMPG:
                stackTypes[stackTop - 4] = INTEGER;
                stackTop -= 3;
                break;
            case Opcode.IFEQ:
            case Opcode.IFNE:
            case Opcode.IFLT:
            case Opcode.IFGE:
            case Opcode.IFGT:
            case Opcode.IFLE:
                stackTop--; // branch
                visitBranch(pos, code, ByteArray.readS16bit(code, pos + 1));
                return 3;
            case Opcode.IF_ICMPEQ:
            case Opcode.IF_ICMPNE:
            case Opcode.IF_ICMPLT:
            case Opcode.IF_ICMPGE:
            case Opcode.IF_ICMPGT:
            case Opcode.IF_ICMPLE:
            case Opcode.IF_ACMPEQ:
            case Opcode.IF_ACMPNE:
                stackTop -= 2; // branch
                visitBranch(pos, code, ByteArray.readS16bit(code, pos + 1));
                return 3;
            case Opcode.GOTO:
                visitGoto(pos, code, ByteArray.readS16bit(code, pos + 1));
                return 3; // branch
            case Opcode.JSR:
                visitJSR(pos, code);
                return 3; // branch
            case Opcode.RET:
                visitRET(pos, code);
                return 2;
            case Opcode.TABLESWITCH:{
                stackTop--; // branch
                int pos2 = (pos & ~3) + 8;
                int low = ByteArray.read32bit(code, pos2);
                int high = ByteArray.read32bit(code, pos2 + 4);
                int n = high - low + 1;
                visitTableSwitch(pos, code, n, pos2 + 8, ByteArray.read32bit(code, pos2 - 4));
                return n * 4 + 16 - (pos & 3);
            }
            case Opcode.LOOKUPSWITCH:{
                stackTop--; // branch
                int pos2 = (pos & ~3) + 8;
                int n = ByteArray.read32bit(code, pos2);
                visitLookupSwitch(pos, code, n, pos2 + 4, ByteArray.read32bit(code, pos2 - 4));
                return n * 8 + 12 - (pos & 3);
            }
            case Opcode.IRETURN:
                stackTop--;
                visitReturn(pos, code);
                break;
            case Opcode.LRETURN:
                stackTop -= 2;
                visitReturn(pos, code);
                break;
            case Opcode.FRETURN:
                stackTop--;
                visitReturn(pos, code);
                break;
            case Opcode.DRETURN:
                stackTop -= 2;
                visitReturn(pos, code);
                break;
            case Opcode.ARETURN:
                stackTypes[--stackTop].setType(returnType, classPool);
                visitReturn(pos, code);
                break;
            case Opcode.RETURN:
                visitReturn(pos, code);
                break;
            case Opcode.GETSTATIC:
                return doGetField(pos, code, false);
            case Opcode.PUTSTATIC:
                return doPutField(pos, code, false);
            case Opcode.GETFIELD:
                return doGetField(pos, code, true);
            case Opcode.PUTFIELD:
                return doPutField(pos, code, true);
            case Opcode.INVOKEVIRTUAL:
            case Opcode.INVOKESPECIAL:
                return doInvokeMethod(pos, code, true);
            case Opcode.INVOKESTATIC:
                return doInvokeMethod(pos, code, false);
            case Opcode.INVOKEINTERFACE:
                return doInvokeIntfMethod(pos, code);
            case Opcode.INVOKEDYNAMIC:
                return doInvokeDynamic(pos, code);
            case Opcode.NEW:{
                int i = ByteArray.readU16bit(code, pos + 1);
                stackTypes[stackTop++] = new TypeData.UninitData(pos, cpool.getClassInfo(i));
                return 3;
            }
            case Opcode.NEWARRAY:
                return doNEWARRAY(pos, code);
            case Opcode.ANEWARRAY:{
                int i = ByteArray.readU16bit(code, pos + 1);
                String type = cpool.getClassInfo(i).replace('.', '/');
                if (type.charAt(0) == '['){
                    type = "[" + type;
                }else{
                    type = "[L" + type + ";";
                }

                stackTypes[stackTop - 1] = new TypeData.ClassName(type);
                return 3;
            }
            case Opcode.ARRAYLENGTH:
                stackTypes[stackTop - 1].setType("[Ljava.lang.Object;", classPool);
                stackTypes[stackTop - 1] = INTEGER;
                break;
            case Opcode.ATHROW:
                stackTypes[--stackTop].setType("java.lang.Throwable", classPool);
                visitThrow(pos, code);
                break;
            case Opcode.CHECKCAST:{
                // TypeData.setType(stackTypes[stackTop - 1], "java.lang.Object", classPool);
                int i = ByteArray.readU16bit(code, pos + 1);
                String type = cpool.getClassInfo(i);
                if (type.charAt(0) == '['){
                    type = type.replace('.', '/'); // getClassInfo() may return "[java.lang.Object;".
                }

                stackTypes[stackTop - 1] = new TypeData.ClassName(type);
                return 3;
            }
            case Opcode.INSTANCEOF:
                // TypeData.setType(stackTypes[stackTop - 1], "java.lang.Object", classPool);
                stackTypes[stackTop - 1] = INTEGER;
                return 3;
            case Opcode.MONITORENTER:
            case Opcode.MONITOREXIT:
                stackTop--;
                // TypeData.setType(stackTypes[stackTop], "java.lang.Object", classPool);
                break;
            case Opcode.WIDE:
                return doWIDE(pos, code);
            case Opcode.MULTIANEWARRAY:
                return doMultiANewArray(pos, code);
            case Opcode.IFNULL:
            case Opcode.IFNONNULL:
                stackTop--; // branch
                visitBranch(pos, code, ByteArray.readS16bit(code, pos + 1));
                return 3;
            case Opcode.GOTO_W:
                visitGoto(pos, code, ByteArray.read32bit(code, pos + 1));
                return 5; // branch
            case Opcode.JSR_W:
                visitJSR(pos, code);
                return 5;
        }
        return 1;
    }

    private int doWIDE(int pos,byte[] code) throws BadBytecode{
        int op = code[pos + 1] & 0xff;
        switch (op) {
            case Opcode.ILOAD:
                doWIDE_XLOAD(pos, code, INTEGER);
                break;
            case Opcode.LLOAD:
                doWIDE_XLOAD(pos, code, LONG);
                break;
            case Opcode.FLOAD:
                doWIDE_XLOAD(pos, code, FLOAT);
                break;
            case Opcode.DLOAD:
                doWIDE_XLOAD(pos, code, DOUBLE);
                break;
            case Opcode.ALOAD:{
                int index = ByteArray.readU16bit(code, pos + 2);
                doALOAD(index);
                break;
            }
            case Opcode.ISTORE:
                doWIDE_STORE(pos, code, INTEGER);
                break;
            case Opcode.LSTORE:
                doWIDE_STORE(pos, code, LONG);
                break;
            case Opcode.FSTORE:
                doWIDE_STORE(pos, code, FLOAT);
                break;
            case Opcode.DSTORE:
                doWIDE_STORE(pos, code, DOUBLE);
                break;
            case Opcode.ASTORE:{
                int index = ByteArray.readU16bit(code, pos + 2);
                doASTORE(index);
                break;
            }
            case Opcode.IINC:
                // this does not call writeLocal().
                return 6;
            case Opcode.RET:
                visitRET(pos, code);
                break;
            default:
                throw new RuntimeException("bad WIDE instruction: " + op);
        }

        return 4;
    }

    private void doWIDE_XLOAD(int pos,byte[] code,TypeData type){
        int index = ByteArray.readU16bit(code, pos + 2);
        doXLOAD(index, type);
    }

    private void doWIDE_STORE(int pos,byte[] code,TypeData type){
        int index = ByteArray.readU16bit(code, pos + 2);
        doXSTORE(index, type);
    }

    private int doPutField(int pos,byte[] code,boolean notStatic) throws BadBytecode{
        int index = ByteArray.readU16bit(code, pos + 1);
        String desc = cpool.getFieldrefType(index);
        stackTop -= Descriptor.dataSize(desc);
        char c = desc.charAt(0);
        if (c == 'L'){
            stackTypes[stackTop].setType(getFieldClassName(desc, 0), classPool);
        }else if (c == '['){
            stackTypes[stackTop].setType(desc, classPool);
        }

        setFieldTarget(notStatic, index);
        return 3;
    }

    private int doGetField(int pos,byte[] code,boolean notStatic) throws BadBytecode{
        int index = ByteArray.readU16bit(code, pos + 1);
        setFieldTarget(notStatic, index);
        String desc = cpool.getFieldrefType(index);
        pushMemberType(desc);
        return 3;
    }

    private void setFieldTarget(boolean notStatic,int index) throws BadBytecode{
        if (notStatic){
            String className = cpool.getFieldrefClassName(index);
            stackTypes[--stackTop].setType(className, classPool);
        }
    }

    private int doNEWARRAY(int pos,byte[] code){
        int s = stackTop - 1;
        String type;
        switch (code[pos + 1] & 0xff) {
            case Opcode.T_BOOLEAN:
                type = "[Z";
                break;
            case Opcode.T_CHAR:
                type = "[C";
                break;
            case Opcode.T_FLOAT:
                type = "[F";
                break;
            case Opcode.T_DOUBLE:
                type = "[D";
                break;
            case Opcode.T_BYTE:
                type = "[B";
                break;
            case Opcode.T_SHORT:
                type = "[S";
                break;
            case Opcode.T_INT:
                type = "[I";
                break;
            case Opcode.T_LONG:
                type = "[J";
                break;
            default:
                throw new RuntimeException("bad newarray");
        }

        stackTypes[s] = new TypeData.ClassName(type);
        return 2;
    }

    private int doMultiANewArray(int pos,byte[] code){
        int i = ByteArray.readU16bit(code, pos + 1);
        int dim = code[pos + 3] & 0xff;
        stackTop -= dim - 1;

        String type = cpool.getClassInfo(i).replace('.', '/');
        stackTypes[stackTop - 1] = new TypeData.ClassName(type);
        return 4;
    }

    private int doInvokeMethod(int pos,byte[] code,boolean notStatic) throws BadBytecode{
        int i = ByteArray.readU16bit(code, pos + 1);
        String desc = cpool.getMethodrefType(i);
        checkParamTypes(desc, 1);
        if (notStatic){
            String className = cpool.getMethodrefClassName(i);
            TypeData target = stackTypes[--stackTop];
            if (target instanceof TypeData.UninitTypeVar && target.isUninit()){
                constructorCalled(target, ((TypeData.UninitTypeVar) target).offset());
            }else if (target instanceof TypeData.UninitData){
                constructorCalled(target, ((TypeData.UninitData) target).offset());
            }

            target.setType(className, classPool);
        }

        pushMemberType(desc);
        return 3;
    }

    /*
     * This is a constructor call on an uninitialized object.
     * Sets flags of other references to that object.
     *
     * @param offset the offset where the object has been created.
     */
    private void constructorCalled(TypeData target,int offset){
        target.constructorCalled(offset);
        for (int i = 0; i < stackTop; i++){
            stackTypes[i].constructorCalled(offset);
        }

        for (TypeData localsType : localsTypes){
            localsType.constructorCalled(offset);
        }
    }

    private int doInvokeIntfMethod(int pos,byte[] code) throws BadBytecode{
        int i = ByteArray.readU16bit(code, pos + 1);
        String desc = cpool.getInterfaceMethodrefType(i);
        checkParamTypes(desc, 1);
        String className = cpool.getInterfaceMethodrefClassName(i);
        stackTypes[--stackTop].setType(className, classPool);
        pushMemberType(desc);
        return 5;
    }

    private int doInvokeDynamic(int pos,byte[] code) throws BadBytecode{
        int i = ByteArray.readU16bit(code, pos + 1);
        String desc = cpool.getInvokeDynamicType(i);
        checkParamTypes(desc, 1);

        // assume CosntPool#REF_invokeStatic
        /*
         * TypeData target = stackTypes[--stackTop];
         * if (target instanceof TypeData.UninitTypeVar && target.isUninit())
         * constructorCalled((TypeData.UninitTypeVar)target);
         */

        pushMemberType(desc);
        return 5;
    }

    private void pushMemberType(String descriptor){
        int top = 0;
        if (descriptor.charAt(0) == '('){
            top = descriptor.indexOf(')') + 1;
            if (top < 1){
                throw new IndexOutOfBoundsException("bad descriptor: " + descriptor);
            }
        }

        TypeData[] types = stackTypes;
        int index = stackTop;
        switch (descriptor.charAt(top)) {
            case '[':
                types[index] = new TypeData.ClassName(descriptor.substring(top));
                break;
            case 'L':
                types[index] = new TypeData.ClassName(getFieldClassName(descriptor, top));
                break;
            case 'J':
                types[index] = LONG;
                types[index + 1] = TOP;
                stackTop += 2;
                return;
            case 'F':
                types[index] = FLOAT;
                break;
            case 'D':
                types[index] = DOUBLE;
                types[index + 1] = TOP;
                stackTop += 2;
                return;
            case 'V':
                return;
            default: // C, B, S, I, Z
                types[index] = INTEGER;
                break;
        }

        stackTop++;
    }

    private static String getFieldClassName(String desc,int index){
        return desc.substring(index + 1, desc.length() - 1).replace('/', '.');
    }

    private void checkParamTypes(String desc,int i) throws BadBytecode{
        char c = desc.charAt(i);
        if (c == ')'){
            return;
        }

        int k = i;
        boolean array = false;
        while (c == '['){
            array = true;
            c = desc.charAt(++k);
        }

        if (c == 'L'){
            k = desc.indexOf(';', k) + 1;
            if (k <= 0){
                throw new IndexOutOfBoundsException("bad descriptor");
            }
        }else{
            k++;
        }

        checkParamTypes(desc, k);
        if (!array && (c == 'J' || c == 'D')){
            stackTop -= 2;
        }else{
            stackTop--;
        }

        if (array){
            stackTypes[stackTop].setType(desc.substring(i, k), classPool);
        }else if (c == 'L'){
            stackTypes[stackTop].setType(desc.substring(i + 1, k - 1).replace('/', '.'), classPool);
        }
    }
}
